<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SSE音频播放Demo</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            background-color: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1 {
            color: #333;
            text-align: center;
        }
        .form-group {
            margin-bottom: 20px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        input, textarea {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            box-sizing: border-box;
        }
        textarea {
            height: 150px;
            resize: vertical;
        }
        button {
            background-color: #007bff;
            color: white;
            padding: 12px 24px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
            width: 100%;
        }
        button:hover {
            background-color: #0056b3;
        }
        button:disabled {
            background-color: #ccc;
            cursor: not-allowed;
        }
        .status {
            margin-top: 20px;
            padding: 15px;
            border-radius: 5px;
            text-align: center;
        }
        .status.connecting {
            background-color: #fff3cd;
            color: #856404;
        }
        .status.connected {
            background-color: #d4edda;
            color: #155724;
        }
        .status.error {
            background-color: #f8d7da;
            color: #721c24;
        }
        .audio-container {
            margin-top: 20px;
            text-align: center;
        }
        .chunk-info {
            margin-top: 10px;
            font-size: 14px;
            color: #666;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>SSE音频播放Demo</h1>
        <form id="ttsForm">
            <div class="form-group">
                <label for="speakerId">说话人ID:</label>
                <input type="text" id="speakerId" value="S_bdVohz3I1" required>
            </div>
            
            <div class="form-group">
                <label for="uid">用户ID:</label>
                <input type="text" id="uid" value="test_1" required>
            </div>
            
            <div class="form-group">
                <label for="text">文本内容:</label>
                <textarea id="text" required>欢迎来到故宫《听宝贝说》！小侦探时间到啦——嘿，各位！我是明成化斗彩三秋杯！我跟你们说啊，我身上可藏着几个让大家猜了好久的谜题，今天咱们就一起化身小侦探，顺着我身上的线索，把这三个"千年陶瓷秘密"一个个揪出来！准备好了没？走起！第一个问题，嗯……你们知道我为什么叫"三秋杯"吗？来来来，看看我外壁上这些图案，有花有石头，还有蝴蝶飞来飞去的。说实话，一开始我自己也没搞懂这画的是哪个季节，后来听专家们一聊才知道——原来这是秋天的景色！因为同时有花、石、蝴蝶这三个元素，干脆就叫我"三秋杯"啦。是不是有点意思？你们之前猜到了吗？第二个谜：我这种"斗彩"到底是咋做出来的？说起这个制作过程，那可真是费老劲了。工匠们得先用青花给我勾线，烧一遍；然后再一点点往上加红彩、黄彩这些颜色，再回炉烧一次，呃……来来回回折腾好几趟。整个过程，就跟解一道巨难的题似的，每一步都得特别小心。不过现在看看，这么费功夫还挺值，不然我哪能这么好看呀，对吧？第三个问题：我是怎么来到故宫的？这事儿就得提孙瀛洲先生了。这位老先生人可真好，不光收了我，还把我和其他两千多件陶瓷一块儿捐给了故宫。现在想想，能在这儿落脚真是我的福气，每天能见到这么多游客，还能跟其他文物小伙伴做邻居，这小日子，过得那叫一个滋润～呃......你们知道吗？孙先生还总结了不少鉴定秘诀，就跟侦探的破案笔记一样，到现在都超好用！小侦探们，你们发现没？其实每一件文物啊……都是一个等着被讲出来的故事。只要我们认真看、仔细听，就能发现那些藏在时间里的秘密。说不定，下一个发现关键线索的小侦探，就是你哦！好啦！今天的《听宝贝说》就先聊到这儿，咱们下个故事再见咯，拜拜～</textarea>
            </div>
            
            <button type="submit" id="submitBtn">开始播放</button>
        </form>
        
        <div id="status" class="status" style="display: none;"></div>
        
        <div class="audio-container">
            <audio id="audioPlayer" controls style="width: 100%;"></audio>
            <div id="chunkInfo" class="chunk-info"></div>
        </div>
    </div>

    <script>
        document.getElementById('ttsForm').addEventListener('submit', function(e) {
            e.preventDefault();
            
            const speakerId = document.getElementById('speakerId').value;
            const uid = document.getElementById('uid').value;
            const text = document.getElementById('text').value;
            
            // 禁用提交按钮
            const submitBtn = document.getElementById('submitBtn');
            submitBtn.disabled = true;
            submitBtn.textContent = '播放中...';
            
            // 清空状态和音频信息
            document.getElementById('status').style.display = 'none';
            document.getElementById('chunkInfo').textContent = '';
            
            // 创建音频上下文
            const audioContext = new (window.AudioContext || window.webkitAudioContext)();
            let audioQueue = [];
            let isPlaying = false;
            let audioSource = null;
            let isProcessing = false; // 添加处理状态标志
            
            // 更新状态显示
            function updateStatus(message, className) {
                const statusDiv = document.getElementById('status');
                statusDiv.textContent = message;
                statusDiv.className = 'status ' + className;
                statusDiv.style.display = 'block';
            }
            
            // 播放音频块（串行播放）
            async function playAudioChunk(audioBuffer) {
                return new Promise((resolve) => {
                    if (audioSource) {
                        audioSource.stop();
                    }
                    
                    audioSource = audioContext.createBufferSource();
                    audioSource.buffer = audioBuffer;
                    audioSource.connect(audioContext.destination);
                    audioSource.start();
                    
                    // 等待当前音频块播放完成
                    audioSource.onended = () => {
                        resolve();
                    };
                });
            }
            
            // 串行处理音频队列
            async function processAudioQueue() {
                // 如果正在处理或没有音频块，则返回
                if (isProcessing || audioQueue.length === 0) {
                    return;
                }
                
                isProcessing = true;
                
                try {
                    while (audioQueue.length > 0) {
                        const audioData = audioQueue.shift();
                        try {
                            const audioBuffer = await audioContext.decodeAudioData(audioData.slice(0));
                            await playAudioChunk(audioBuffer);
                            document.getElementById('chunkInfo').textContent = `已播放，队列中还有 ${audioQueue.length} 个音频块`;
                        } catch (e) {
                            console.error('Error decoding or playing audio data:', e);
                        }
                    }
                } finally {
                    isProcessing = false;
                }
            }
            
            // 播放第一个音频块（串行播放）
            async function playFirstAudioChunk() {
                if (audioQueue.length > 0 && !isPlaying) {
                    isPlaying = true;
                    const audioData = audioQueue.shift();
                    try {
                        const audioBuffer = await audioContext.decodeAudioData(audioData.slice(0));
                        await playAudioChunk(audioBuffer);
                        document.getElementById('chunkInfo').textContent = `已播放，队列中还有 ${audioQueue.length} 个音频块`;
                        // 继续处理剩余的音频块
                        processAudioQueue();
                    } catch (e) {
                        console.error('Error decoding first audio chunk:', e);
                    } finally {
                        isPlaying = false;
                    }
                }
            }
            
            // 使用POST请求发送TTS数据
            const host = 'http://180.184.36.0:9101';
            const url = `${host}/tts`;
            
            updateStatus('正在发送请求...', 'connecting');
            
            // 发送POST请求
            console.log('Sending request to:', url);
            console.log('Request body:', JSON.stringify({
                speakerId: speakerId,
                uid: uid,
                text: text
            }));
            
            fetch(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'text/event-stream',
                    'Cache-Control': 'no-cache'
                },
                body: JSON.stringify({
                    speakerId: speakerId,
                    uid: uid,
                    text: text
                })
            })
            .then(response => {
                // 即使响应不是200，我们也继续处理流数据
                console.log('Response status:', response.status);
                console.log('Response headers:', [...response.headers.entries()]);
                
                updateStatus('正在接收音频数据...', 'connected');
                
                // 处理SSE流式响应
                const reader = response.body.getReader();
                const decoder = new TextDecoder('utf-8');
                let buffer = '';
                
                function readStream() {
                    reader.read().then(({ done, value }) => {
                        if (done) {
                            updateStatus('音频播放完成', 'connected');
                            submitBtn.disabled = false;
                            submitBtn.textContent = '开始播放';
                            // 处理完所有数据后开始播放
                            processAudioQueue();
                            return;
                        }
                        
                        // 将接收到的数据添加到缓冲区
                        buffer += decoder.decode(value, { stream: true });
                        
                        // 处理SSE事件
                        const lines = buffer.split('\n');
                        buffer = lines.pop(); // 保留不完整的行
                        
                        for (const line of lines) {
                            if (line.startsWith('data: ')) {
                                const data = line.slice(6); // 移除 'data: ' 前缀
                                try {
                                    // 检查数据是否为空或无效
                                    if (!data || data.trim() === '') {
                                        console.warn('Received empty data chunk');
                                        continue;
                                    }
                                    
                                    // 尝试解析JSON数据
                                    let audioData;
                                    try {
                                        const jsonData = JSON.parse(data);
                                        audioData = jsonData.audioData;
                                        console.log('Frame type:', jsonData.frameType);
                                    } catch (jsonError) {
                                        // 如果不是JSON格式，假设是直接的Base64数据
                                        audioData = data;
                                    }
                                    
                                    // 检查audioData是否存在且不为空
                                    if (!audioData || audioData.trim() === '') {
                                        console.warn('Received empty audio data');
                                        continue;
                                    }
                                    
                                    // 解析Base64音频数据
                                    const binaryString = atob(audioData);
                                    const bytes = new Uint8Array(binaryString.length);
                                    for (let i = 0; i < binaryString.length; i++) {
                                        bytes[i] = binaryString.charCodeAt(i);
                                    }
                                    
                                    // 将音频数据加入队列
                                    audioQueue.push(bytes.buffer);
                                    
                                    // 更新接收到的块数
                                    document.getElementById('chunkInfo').textContent = `已接收 ${audioQueue.length} 个音频块`;
                                    
                                    // 如果是第一个音频块且没有在播放，立即开始播放
                                    if (audioQueue.length === 1 && !isPlaying && !isProcessing) {
                                        playFirstAudioChunk();
                                    }
                                    // 如果已经在播放，确保继续处理队列
                                    else if (isPlaying && !isProcessing) {
                                        processAudioQueue();
                                    }
                                } catch (e) {
                                    console.error('Error processing audio chunk:', e);
                                    console.error('Problematic data:', data);
                                }
                            } else if (line === 'event: end') {
                                // 音频流结束
                                updateStatus('音频播放完成', 'connected');
                                submitBtn.disabled = false;
                                submitBtn.textContent = '开始播放';
                                // 确保所有音频块都已播放
                                if (!isProcessing && audioQueue.length > 0) {
                                    processAudioQueue();
                                }
                            }
                        }
                        
                        // 继续读取数据
                        readStream();
                    }).catch(error => {
                        updateStatus('接收数据错误: ' + error.message, 'error');
                        submitBtn.disabled = false;
                        submitBtn.textContent = '开始播放';
                    });
                }
                
                // 开始读取数据流
                readStream();
            })
            .catch(error => {
                updateStatus('请求错误: ' + error.message, 'error');
                submitBtn.disabled = false;
                submitBtn.textContent = '开始播放';
            });
        });
    </script>
</body>
</html>